package com.bolt.support.query.toolkit;

import com.bolt.common.bean.BeanUtil;
import com.bolt.common.reflect.ClassUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.convention.data.Record;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.spring.jpa.jpql.ColumnClause;
import org.hibernate.Hibernate;
import org.hibernate.proxy.HibernateProxy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapperImpl;

import javax.persistence.Parameter;
import javax.persistence.Query;
import javax.persistence.Transient;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.util.*;

public class JPAUtil {

    protected final static Logger logger = LoggerFactory.getLogger(JPAUtil.class);

    public static ColumnClause[] createSelect(Class<?> clazz) {
        try {
            PropertyDescriptor[] propertyDescriptors = BeanUtil.getPropertyDescriptors(clazz);
            List<ColumnClause> clauses = new ArrayList<>();
            List<Field> allFields = ClassUtil.getAllFields(clazz);
            for (int i = 0; i < propertyDescriptors.length; i++) {
                PropertyDescriptor property = propertyDescriptors[i];
                Optional<Field> field = allFields.stream().filter(k -> k.getName().equals(property.getName())).findFirst();
                if (property.getWriteMethod() != null && field.isPresent() && !field.get().isAnnotationPresent(Transient.class)) {
                    clauses.add(new ColumnClause(property.getName(), clazz));
                }
            }
            return clauses.toArray(new ColumnClause[]{});
        } catch (Exception ex) {
            throw new ServiceException(ex);
        }

    }

    public static void setParameter(Query query, Properties properties) {
        Set<Parameter<?>> parameters = query.getParameters();
        parameters.forEach((k) -> {
            String paramName = k.getName();
            if (StrUtil.isNotBlank(paramName) && properties.containsKey(paramName)) {
                query.setParameter(paramName, properties.get(paramName));
            }
        });
    }

    public static Object toBean(Object[] source, Class<?> returnType, List<String> aliases, Map<String, Field> accessFields) {
        if (Record.class.isAssignableFrom(returnType)) {
            return toRecord(source, aliases);
        } else {
            return beanMapper(source, returnType, aliases, accessFields);
        }
    }


    public static Object beanMapper(Object[] source, Class<?> returnType, List<String> aliases, Map<String, Field> accessFields) {

        BeanWrapperImpl bw = new BeanWrapperImpl();
        Object mappedObject = ClassUtil.newInstance(returnType);
        bw.setBeanInstance(mappedObject);
        for (int i = 0; i < source.length; i++) {
            String propertyName = aliases.get(i);
            if (StrUtil.isBlank(propertyName) || accessFields.get(propertyName) == null || !bw.isWritableProperty(propertyName)) {
                continue;
            }
            Object value = source[i];
            bw.setPropertyValue(propertyName, value);
        }
        return mappedObject;
    }

    public static Object toRecord(Object[] source, List<String> aliases) {
        Record record = new Record();
        for (int i = 0; i < source.length; i++) {
            String alias = aliases.get(i);
            record.put(alias.toUpperCase(), source[i]);
        }
        return record;
    }

    public static int findColumnIndex(List<String> aliases, String columnName) {
        int index = 0;
        for (int i = 0; i < aliases.size(); i++) {
            String alias = aliases.get(i);
            if (StrUtil.equalsIgnoreCase(alias, columnName)) {
                index = i;
                break;
            }
        }
        return index;
    }

    public static <T> T initializeAndUnproxy(T entity) {
        if (entity == null) {
            throw new
                    NullPointerException("Entity passed for initialization is null");
        }
        Hibernate.initialize(entity);
        if (entity instanceof HibernateProxy) {
            entity = (T) ((HibernateProxy) entity).getHibernateLazyInitializer().getImplementation();
        }
        return entity;
    }

}

 